package com.aptech.util;

import static java.util.Locale.ENGLISH;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import javax.xml.datatype.XMLGregorianCalendar;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.FatalBeanException;

@SuppressWarnings("unchecked")
public class Converter {

	public static <T> T newInstance(Class<?> clazz) {
		try {
			Object newobj = clazz.getConstructor().newInstance();
			return (T) newobj; // unchecked cast
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	public static <S,T> List<T> getList(Collection<S> sourceList, Class<T> actualEditable) {
		return getList(sourceList, actualEditable, null);
	}

	public static <S,T> List<T> getList(Collection<S> sourceList, Class<T> actualEditable, String[] ignoreProperties) {
		List<T> resultList = new ArrayList<T>();

		if (sourceList != null && sourceList.size() > 0) {
			for (S s : sourceList) {
				T target = get(s, actualEditable, ignoreProperties);
				resultList.add(target);
			}
		}

		return resultList;
	}

	public static <S,T> T get(S source, Class<T> actualEditable) {
		return get(source, actualEditable, null);
	}

	public static <S,T> T get(S source, Class<T> actualEditable, String[] ignoreProperties) {
		if (source == null) {
			return newInstance(actualEditable);
		}

        T target = newInstance(actualEditable);
        PropertyDescriptor targetPds[] = BeanUtils.getPropertyDescriptors(actualEditable);
        List ignoreList = ignoreProperties == null ? null : Arrays.asList(ignoreProperties);

        for(int i = 0; i < targetPds.length; i++)
        {
        	PropertyDescriptor targetPd = targetPds[i];
            if(targetPd.getWriteMethod() == null || ignoreProperties != null && ignoreList.contains(targetPd.getName()))
                continue;
            PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
            if(sourcePd == null || getReadMethod(source, sourcePd) == null)
                continue;
            
            if ((sourcePd.getPropertyType() != targetPd.getPropertyType()) && 
            		(sourcePd.getPropertyType() != Date.class) && (sourcePd.getPropertyType() != XMLGregorianCalendar.class) &&
            		(targetPd.getPropertyType() != Date.class) && (targetPd.getPropertyType() != XMLGregorianCalendar.class)) {
            	continue;
            }
            try {

                Method readMethod = getReadMethod(source, sourcePd);
                if(!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()))
                    readMethod.setAccessible(true);
                Object value = readMethod.invoke(source, new Object[0]);
                Method writeMethod = targetPd.getWriteMethod();
                if(!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()))
                    writeMethod.setAccessible(true);

                if (targetPd.getPropertyType() == Date.class && sourcePd.getPropertyType() == XMLGregorianCalendar.class) {
                	writeMethod.invoke(target, new Object[] {
                			BaseBeanConverter.convertXMLDate((XMLGregorianCalendar)value)
    	                });
                } else if (targetPd.getPropertyType() == XMLGregorianCalendar.class && sourcePd.getPropertyType() == Date.class) {
                	writeMethod.invoke(target, new Object[] {
                			BaseBeanConverter.convertToXMLDate((Date)value)
    	                });
                } else {
                	// default
	                writeMethod.invoke(target, new Object[] {
	                    value
	                });
                }
            } catch(Throwable ex) {
                throw new FatalBeanException("Could not copy properties from source to target", ex);
            }
        }

        return target;
	}
	
	public static <S,T> T get(S source, Class<T> actualEditable, String[] ignoreProperties, String [] includeProperties) {
		if (source == null) {
			return newInstance(actualEditable);
		}

        T target = newInstance(actualEditable);
        PropertyDescriptor targetPds[] = BeanUtils.getPropertyDescriptors(actualEditable);
        List ignoreList = ignoreProperties == null ? null : Arrays.asList(ignoreProperties);
        List includeList = includeProperties == null ? null : Arrays.asList(includeProperties);

        for(int i = 0; i < targetPds.length; i++)
        {
        	PropertyDescriptor targetPd = targetPds[i];
            if(targetPd.getWriteMethod() == null || (ignoreProperties != null && ignoreList.contains(targetPd.getName()))
            		|| (includeList != null && !includeList.contains(targetPd.getName())))
                continue;
            PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
            if(sourcePd == null || getReadMethod(source, sourcePd) == null)
                continue;
            
            if ((sourcePd.getPropertyType() != targetPd.getPropertyType()) && 
            		(sourcePd.getPropertyType() != Date.class) && (sourcePd.getPropertyType() != XMLGregorianCalendar.class) &&
            		(targetPd.getPropertyType() != Date.class) && (targetPd.getPropertyType() != XMLGregorianCalendar.class)) {
            	continue;
            }
            try {

                Method readMethod = getReadMethod(source, sourcePd);
                if(!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()))
                    readMethod.setAccessible(true);
                Object value = readMethod.invoke(source, new Object[0]);
                Method writeMethod = targetPd.getWriteMethod();
                if(!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()))
                    writeMethod.setAccessible(true);

                if (targetPd.getPropertyType() == Date.class && sourcePd.getPropertyType() == XMLGregorianCalendar.class) {
                	writeMethod.invoke(target, new Object[] {
                			BaseBeanConverter.convertXMLDate((XMLGregorianCalendar)value)
    	                });
                } else if (targetPd.getPropertyType() == XMLGregorianCalendar.class && sourcePd.getPropertyType() == Date.class) {
                	writeMethod.invoke(target, new Object[] {
                			BaseBeanConverter.convertToXMLDate((Date)value)
    	                });
                } else {
                	// default
	                writeMethod.invoke(target, new Object[] {
	                    value
	                });
                }
            } catch(Throwable ex) {
                throw new FatalBeanException("Could not copy properties from source to target", ex);
            }
        }

        return target;
	}
	

	public static <S,T> T merge(S source, T target, Class<T> actualEditable){
		return merge(source, target, actualEditable, null);
	}

	public static <S,T> T merge(S source, T target, Class<T> actualEditable, String[] ignoreProperties) {
		if (source == null && target == null) {
			return newInstance(actualEditable);
		} else if (source == null) {
			return target;
		}

        PropertyDescriptor targetPds[] = BeanUtils.getPropertyDescriptors(actualEditable);
        List ignoreList = ignoreProperties == null ? null : Arrays.asList(ignoreProperties);

        for(int i = 0; i < targetPds.length; i++)
        {
        	PropertyDescriptor targetPd = targetPds[i];
            if(targetPd.getWriteMethod() == null || ignoreProperties != null && ignoreList.contains(targetPd.getName()))
                continue;
            PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
            if(sourcePd == null || getReadMethod(source, sourcePd) == null)
                continue;

            try {
                Method readMethod = getReadMethod(source, sourcePd);
                if(!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()))
                    readMethod.setAccessible(true);
                Object value = readMethod.invoke(source, new Object[0]);
                Method writeMethod = targetPd.getWriteMethod();
                if(!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()))
                    writeMethod.setAccessible(true);

                if (value != null) {
	                if (targetPd.getPropertyType() == Date.class && sourcePd.getPropertyType() == XMLGregorianCalendar.class) {
	                	writeMethod.invoke(target, new Object[] {
	                			BaseBeanConverter.convertXMLDate((XMLGregorianCalendar)value)
	    	                });
	                } else if (targetPd.getPropertyType() == XMLGregorianCalendar.class && sourcePd.getPropertyType() == Date.class) {
	                	writeMethod.invoke(target, new Object[] {
	                			BaseBeanConverter.convertToXMLDate((Date)value)
	    	                });
	                } else {
	                	// default
		                writeMethod.invoke(target, new Object[] {
		                    value
		                });
	                }
                }
            } catch(Throwable ex) {
                throw new FatalBeanException("Could not merge properties from source to target", ex);
            }
        }


        return target;
	}

	public static <S,T> T merge(S source, T target, Class<T> actualEditable, String[] ignoreProperties, String[] includeProperties) {
		if (source == null && target == null) {
			return newInstance(actualEditable);
		} else if (source == null) {
			return target;
		}
		
		List includeList = includeProperties == null ? null : Arrays.asList(includeProperties);
		
		PropertyDescriptor targetPds[] = BeanUtils.getPropertyDescriptors(actualEditable);
		List ignoreList = ignoreProperties == null ? null : Arrays.asList(ignoreProperties);
		
		for(int i = 0; i < targetPds.length; i++)
		{
			PropertyDescriptor targetPd = targetPds[i];
			if(targetPd.getWriteMethod() == null || ignoreProperties != null && ignoreList.contains(targetPd.getName())
					|| (includeList != null && !includeList.contains(targetPd.getName())))
				continue;
			
			PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
			if(sourcePd == null || getReadMethod(source, sourcePd) == null)
				continue;
			
			try {
				Method readMethod = getReadMethod(source, sourcePd);
				if(!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()))
					readMethod.setAccessible(true);
				Object value = readMethod.invoke(source, new Object[0]);
				Method writeMethod = targetPd.getWriteMethod();
				if(!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()))
					writeMethod.setAccessible(true);
				
				if (value != null) {
					if (targetPd.getPropertyType() == Date.class && sourcePd.getPropertyType() == XMLGregorianCalendar.class) {
						writeMethod.invoke(target, new Object[] {
								BaseBeanConverter.convertXMLDate((XMLGregorianCalendar)value)
						});
					} else if (targetPd.getPropertyType() == XMLGregorianCalendar.class && sourcePd.getPropertyType() == Date.class) {
						writeMethod.invoke(target, new Object[] {
								BaseBeanConverter.convertToXMLDate((Date)value)
						});
					} else {
						// default
						writeMethod.invoke(target, new Object[] {
								value
						});
					}
				}
			} catch(Throwable ex) {
				throw new FatalBeanException("Could not merge properties from source to target", ex);
			}
		}
		
		
		return target;
	}
		
	public static <S> Method getReadMethod(S source, PropertyDescriptor sourcePd) {
		Method readMethod = null;
		
		if (sourcePd != null) {
			Class type = sourcePd.getPropertyType();
			String readMethodName;
			if (type == boolean.class || type== Boolean.class || type == null) {
			    readMethodName = "is" + capitalize(sourcePd.getName());
			} else {
			    readMethodName = "get" + capitalize(sourcePd.getName());
			}
			readMethod = BeanUtils.findMethod(source.getClass(), readMethodName, null);
		    if (readMethod == null) {
				readMethodName = "get" + capitalize(sourcePd.getName());
				readMethod = BeanUtils.findMethod(source.getClass(), readMethodName, null);
		    }
		}
		
		return readMethod;
	}
	
	public static String capitalize(String name) {
		if (name == null || name.length() == 0) {
			return name;
		}
		return name.substring(0, 1).toUpperCase(ENGLISH) + name.substring(1);
	}

	public static <S> List<S> convertArrayList(Collection<S> sourceList, Class<S> audit) {
		List<S> resultList = new ArrayList<S>();
		if (sourceList != null && sourceList.size() > 0) {
			for (S s : sourceList) {
				resultList.add(s);
			}
		}

		return resultList;
	}

}